#include "converter.h"
#include "exceptions.h"
#include "global_variables.h"
#include "str.h"

//#ifdef WIN32
//  #define LONG_LONG_FORMAT "%I64d"
//  #define UNSIGNED_LONG_LONG_FORMAT "%I64u"
//#else
  #define LONG_LONG_FORMAT "%lld"
  #define UNSIGNED_LONG_LONG_FORMAT "%llu"
//#endif

#define BUFFER_SIZE 32 // will be used for sprintf

template <class EXPECTED_TYPE, int MIN_VALUE, unsigned int MAX_VALUE, class VALUE_TYPE>
static EXPECTED_TYPE safe_number_cast(const VALUE_TYPE &value) {
  if (value < MIN_VALUE || value > MAX_VALUE) {
    string msg = "Possible data loss while converting to a number! Current value = %1, min = %2, max = %3";
    str::format(msg, arg<VALUE_TYPE, int, unsigned int>(value, MIN_VALUE, MAX_VALUE));
    throw parameter_exception(msg);
  } else
    return (EXPECTED_TYPE) value;
} // safe_number_cast

////

long long converter::power_10(short i) {
  long long result = 1;
  for (; i != 0; --i)
    result *= 10;

  return result;
} // converter::power_10

void converter::check_number(const string &input, bool can_be_negative, bool can_be_double) {
  const char *allowed_characters = "0123456789";

  if (can_be_double == true)
    allowed_characters = "0123456789-+.eE";
  else if (can_be_negative == true)
    allowed_characters = "0123456789-";

  const size_t len = input.length();
  for (size_t i = 0; i < len; ++i) {
    if (strchr(allowed_characters, input[i]) == 0) {
      string msg = "The input string '%1' could not be converted to a number!";
      str::format(msg, arg<string>(input));
      throw parameter_exception(msg);
    } // if
  } // for i
} // converter::check_number

void converter::to_char(int i, char *s) {
  to_char((long long) i, s);
} // converter::to_char

void converter::to_char(long long i, char *s) {
  const int count = sprintf(s, LONG_LONG_FORMAT, i);
  if (count <= 0)
    throw parameter_exception(global_empty_string);

} // converter::to_char

void converter::to_char(const string &s1, char *s2) {
  strncpy(s2, s1.c_str(), s1.length());
  s2[s1.length()] = '\0';
} // converter::to_char

short converter::to_short(const string &s) {
  return safe_number_cast<short, SHRT_MIN, SHRT_MAX>(to_long_long(s));
} // converter::to_short

unsigned short converter::to_unsigned_short(const string &s) {
  return safe_number_cast<unsigned short, 0, USHRT_MAX>(to_unsigned_long_long(s));
} // converter::to_unsigned_short

int converter::to_int(const string &s) {
  return safe_number_cast<int, INT_MIN, INT_MAX>(to_long_long(s));
} // converter::to_int

unsigned int converter::to_unsigned_int(const string &s) {
  return safe_number_cast<unsigned int, 0, UINT_MAX>(to_unsigned_long_long(s));
} // converter::to_unsigned_int

long long converter::to_long_long(const string &s) {
  long long result = 0LL;
  if (s.length() == 0)
    return result;

  check_number(s, true, false);

  const int count = sscanf(s.c_str(), LONG_LONG_FORMAT, &result);
  if (count != 1)
    throw parameter_exception(global_empty_string);

  if (s[0] == '-') {
    if (s.length() > 20)
      throw parameter_exception(global_empty_string); // long long can be up to 19 characters, or 20 characters including '-' for negative numbers

  } else if (s.length() > 19)
    throw parameter_exception(global_empty_string);

  return result;
} // converter::to_long_long

unsigned long long converter::to_unsigned_long_long(const string &s) {
  unsigned long long result = 0ULL;
  if (s.length() == 0)
    return result;

  check_number(s, false, false);

  const int count = sscanf(s.c_str(), UNSIGNED_LONG_LONG_FORMAT, &result);
  if (count != 1 || s.length() > 20)
    throw parameter_exception(global_empty_string);

  return result;
} // converter::to_unsigned_long_long

double converter::to_double(const string &s) {
  double result = 0.0;
  if (s.length() == 0)
    return result;

  check_number(s, true, true);

  const int count = sscanf(s.c_str(), "%lf", &result);
  if (count != 1)
    throw parameter_exception(global_empty_string);

  return result;
} // converter::to_double

string &converter::to_string(char c, string &output) {
  output.assign(1, c);
  return output;
} // converter::to_string

string &converter::to_string(int i, string &output) {
  return to_string((long long) i, output);
} // converter::to_string

string &converter::to_string(unsigned int i, string &output) {
  return to_string((unsigned long long) i, output);
} // converter::to_string

string &converter::to_string(long long i, string &output) {
  char buffer[BUFFER_SIZE + 1] = { "\0" };
  const int count = sprintf(buffer, LONG_LONG_FORMAT, i);
  if (count > 0)
    output.assign(buffer, count);
  else
    throw parameter_exception(global_empty_string);

  return output;
} // converter::to_string

string &converter::to_string(unsigned long long i, string &output) {
  char buffer[BUFFER_SIZE + 1] = { "\0" };
  const int count = sprintf(buffer, UNSIGNED_LONG_LONG_FORMAT, i);
  if (count > 0)
    output.assign(buffer, count);
  else
    throw parameter_exception(global_empty_string);

  return output;
} // converter::to_string

string &converter::to_string(double d, string &output, unsigned short after_comma) {
  char buffer[BUFFER_SIZE + 1] = { "\0" };

  unsigned short before_comma_digits = 0;
  if (abs(d) >= 1.0)
    before_comma_digits = ((unsigned short) log10(abs(d))) + 1;

  const int count = sprintf(buffer, "%.*g", min<unsigned short>(15, before_comma_digits + after_comma), d); // "%.*f" as format may cause problems if the input is for example 1e300
  if (count > 0)
    output.assign(buffer, count);
  else
    throw parameter_exception(global_empty_string);

  return output;
} // converter::to_string

string &converter::to_string(const string &s, string &output) {
  output = s;
  return output;
} // converter::to_string

long long converter::round_long(long long i, short j) {
  short sign = +1;

  if (i == 0)
    return 0;

  if (i < 0) {
    sign = -1;
    i = -i;
  } // if

  const short len_i = (short) (log10((double) i)) + 1;

  if (len_i > j) {
    const long long power = power_10(len_i - j);
    i /= power;
  } // if

  short f = (short) (i % 10);
  if (f >= 5)
    i += (10 - f);
  else
    i -= f;

  return (i * sign);
} // converter::round_long

double converter::round_double(double d, short j) {
  short sign = 1;
  short f;
  long long a;
  long long b;
  const double power = (double) power_10(j);

  if (d < 0) {
    sign = -1;
    d = -d;
  } // if

  a = (long long) d;                 // before comma
  b = (long long) ((d - a) * power); // after comma

  f = (short) (b % 10);

  if (f >= 5)
    b += 10 - f;
  else
    b -= f;

  return ((a + b / power) * sign);
} // converter::round_double

string &converter::to_upper(const string &s, string &output) {
  output.resize(0);
  output.reserve(s.length());
  transform(s.begin(), s.end(), back_inserter(output), ::toupper); // do not use std::toupper
  return output;
} // converter::to_upper

string &converter::to_lower(const string &s, string &output) {
  output.resize(0);
  output.reserve(s.length());
  transform(s.begin(), s.end(), back_inserter(output), ::tolower); // do not use std::tolower
  return output;
} // converter::to_lower

string &converter::thousand_separator(const string &s, string &output) {
  short counter = 0;
  bool is_negative = false;
  output.resize(0);
  string temp;

  if (s.length() == 0)
    return output;

  if (s.find(' ') != string::npos) {
    output = s;
    return output; // if there are any spaces in input, we return it without any change
  } // if

  if (s[0] == '-') {
    is_negative = true;
    temp.assign(s, 1, s.length() - 1);
  } else
    temp = s;

  size_t i = temp.find('e');

  if (i != string::npos) {
    output.assign(temp, i, temp.length() - i);
    temp.erase(i);
  } // if

  i = temp.find('.');
  if (i == string::npos)
    i = temp.length() - 1;
  else {
    output.insert(0, temp, i, temp.length() - i);
    --i;
  } // else

  while (i != string::npos) {
    counter++;
    output.insert(0, 1, temp[i]);

    if (counter == 3 && i > 0) {
      output.insert(0, 1, ',');
      counter = 0;
    } // if

    --i;
  } // while

  if (is_negative == true)
    output.insert(0, 1, '-');

  return output;
} // converter::thousand_separator

string &converter::remove_all(const string &s, char c, string &output) {
  output.resize(0);
  std::remove_copy(s.begin(), s.end(), back_inserter(output), c);
  return output;
} // converter::remove_all

string &converter::replace_all(const string &s, char c1, char c2, string &output) {
  output.resize(0);
  output.reserve(s.length());
  std::replace_copy(s.begin(), s.end(), back_inserter(output), c1, c2);
  return output;
} // converter::replace_all

string &converter::replace_all(const string &s, const string &s1, const string &s2, string &output) {
  const size_t len1 = s1.length();
  const size_t len2 = s2.length();
  size_t pos = 0;
  output = s;

  if (len1 == 0)
    return output;

  do {
    pos = output.find(s1, pos);
    if (pos != string::npos)
      output.replace(pos, len1, s2);
    else
      break;

    pos += len2; // avoids endless loops, plus better performance
  } while (true);

  return output;
} // converter::replace_all

void converter::trim(string &s, const string &trim_characters, trim_operation to) {
  if (s.length() == 0)
    return;

  size_t len = s.length();
  size_t i = 0;

  if (to == trim_left || to == trim_left_and_right) {
    while (i < len && trim_characters.find(s[i]) != string::npos)
      ++i;
  } //if

  --len;

  if (to == trim_right || to == trim_left_and_right) {
    while (len > i && trim_characters.find(s[len]) != string::npos)
      --len;
  } // if

  s.erase(len + 1);
  s.erase(0, i);
} // converter::trim

string &converter::to_utf8(const wchar_t *ws, string &output) {
  output.resize(0);
  unsigned int c = *ws;

  while (c != 0) {
    if (c <= 127) { // one byte
      output += (char) c;
    } else if (c >= 128 && c <= 2047) { // two bytes
      output += (c >> 6) | 0xC0;
      output += (c & 0x3F) | 0x80;
    } else if (c >= 2048 && c <= 65535) { // three bytes
      output += ((c >> 12) & 0xF) | 0xE0;
      output += ((c >> 6) & 0x3F) | 0x80;
      output += (c & 0x3F) | 0x80;
    } else if (c >= 65536 && c <= 2097151) { // four bytes
      output += ((c >> 18) & 0x7) | 0xF0;
      output += ((c >> 12) & 0x3F) | 0x80;
      output += ((c >> 6) & 0x3F) | 0x80;
      output += (c & 0x3F) | 0x80;
    } // else

    ++ws;
    c = *ws;
  } // while

  return output;
} // converter::to_utf8

string &converter::to_utf8(const wstring &ws, string &output) {
  return to_utf8(ws.c_str(), output);
} // converter::to_utf8

wstring &converter::to_ucs2(const char *s, wstring &output) {
  // utf-8 encoded

  // using one byte, 0zzzzzzz
  //                 7F

  // using two bytes, 110yyyyy 10zzzzzz
  //                  1F       3F

  // using three bytes, 1110xxxx 10yyyyyy 10zzzzzz
  //                    F        3F       3F

  // using four bytes, 11110www 10xxxxxx 10yyyyyy 10zzzzzz
  //                   7        3F       3F       3F

  output.resize(0);
  unsigned char c1, c2, c3, c4;
  c1 = *s;

  while (c1 != 0) {
    if (c1 >= 194 && c1 <= 223) { // two bytes
      ++s;
      c2 = *s;
      output += (((c1 & 0x1f) << 6) + (c2 & 0x3f));
      ++s;
    } else if (c1 >= 224 && c1 <= 239) { // three bytes
      ++s;
      c2 = *s;
      ++s;
      c3 = *s;
      output += (((c1 & 0xf) << 12) + ((c2 & 0x3f) << 6) + (c3 & 0x3f));
      ++s;
    } else if (c1 >= 240 && c1 <= 244) { // four bytes
      ++s;
      c2 = *s;
      ++s;
      c3 = *s;
      ++s;
      c4 = *s;
      output += (((c1 & 0x7) << 18) + ((c2 & 0x3f) << 12) + ((c3 & 0x3f) << 6) + (c4 & 0x3f));
      ++s;
    } else { // one byte or (c1 >= 245) which would actually be a mistake but the c1 should be copied to the output
      output += c1;
      ++s;
    } // else

    c1 = *s;
  } // while

  return output;
} // converter::to_ucs2

wstring &converter::to_ucs2(const string &s, wstring &output) {
  return to_ucs2(s.c_str(), output);
} // converter::to_ucs2
